package eo.builder.impl;

import java.io.File;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import eo.application.ApplicationConfiguration;
import eo.builder.ApplicationBuilder;

public abstract class AbstractApplicationBuilder implements ApplicationBuilder, Serializable {

    private List<URL> classpath = Collections
            .synchronizedList(new LinkedList<URL>());

    private boolean defaultCpSet = false;
    
    public final void addDefaultClasspathEntries() {
        if (defaultCpSet) {
            return;
        }
        List<String> list = this.getDefaultClasspathResourceNames();
        for (String string : list) {

            URL resource = this.getClass().getResource(string);
            if (resource != null) {
                this.addToClasspath(resource);
                continue;
            }
            resource = this.getClass().getClassLoader().getResource(string);
            if (resource != null) {
                this.addToClasspath(resource);
                continue;
            }
            resource = Thread.currentThread().getContextClassLoader()
                    .getResource(string);
            if (resource != null) {
                this.addToClasspath(resource);
                continue;
            }
            File file = new File(string);
            if (file.exists()) {
                this.addToClasspath(file);
                continue;
            }
            throw new IllegalArgumentException("No such resource: " + resource);
        }
        this.defaultCpSet = true;
    }

    public final void addToClasspath(final File file) {
        try {
            this.addToClasspath(file.toURI().toURL());
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException(e);
        }
    }

    public final void addToClasspath(final URL url) {
        if (!this.classpath.contains(url)) {
            this.classpath.add(url);
        }
    }

    public final ApplicationConfiguration buildApplication()
            throws ClassNotFoundException, InstantiationException,
            IllegalAccessException {
        this.addDefaultClasspathEntries();
        List<URL> localCP = new ArrayList<URL>(this.classpath);
        ClassLoader appClassLoader = createClassLoader(localCP);
        Class<?> implementor = appClassLoader.loadClass(this
                .getApplicationClassName());
        ApplicationConfiguration instance = (ApplicationConfiguration) implementor
                .newInstance();
        return instance;
    }

    private ClassLoader createClassLoader(List<URL> localCP) {
        ClassLoader appClassLoader = new URLClassLoader(localCP
                .toArray(new URL[localCP.size()]));
        return appClassLoader;
    }

    public List<URL> getClasspathEntries() {
        return new ArrayList<URL>(this.classpath);
    }

    /**
     * Gets the parent classloader. Defaults to this class' ClassLoader.
     * Implementors might want to use the {@link Thread#getContextClassLoader()}
     * instead.
     * 
     * @return {@link ClassLoader}
     */
    protected ClassLoader getParentClassLoader() {
        return this.getClass().getClassLoader();
    }

    protected abstract List<String> getDefaultClasspathResourceNames();

    protected abstract String getApplicationClassName();
}
